﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Consul;
using MicroContainers.AspCore.Consul.HealthCheck;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;

namespace MicroContainers.AspCore.Consul
{
    public static class ConsulExtensions
    {
        public static IApplicationBuilder RegisterWithConsul(this IApplicationBuilder app, IApplicationLifetime lifetime, List<IHeathCheckConfig> checkConfigs = null)
        {
            // Retrieve Consul client from DI
            var consulClient = app.ApplicationServices
                                .GetRequiredService<IConsulClient>();
            var consulConfig = app.ApplicationServices
                                .GetRequiredService<IOptions<ConsulConfig>>();
            // Setup logger
            var loggingFactory = app.ApplicationServices
                                .GetRequiredService<ILoggerFactory>();
            var logger = loggingFactory.CreateLogger<IApplicationBuilder>();

            Uri uri = null;
            if (string.IsNullOrEmpty(consulConfig.Value.ServiceAddress))
            {
                // Get server IP address
                var features = app.Properties["server.Features"] as FeatureCollection;
                var addresses = features.Get<IServerAddressesFeature>();
                var address = addresses.Addresses.First();

                // Register service with consul
                uri = new Uri(address);
            }
            else
            {
                uri = new Uri(consulConfig.Value.ServiceAddress);
            }

            var registration = new AgentServiceRegistration()
            {
                ID = $"{consulConfig.Value.ServiceID}-{uri.Port}",
                Name = consulConfig.Value.ServiceName,
                Address = $"{uri.Scheme}://{uri.Host}",
                Port = uri.Port,
                Tags = consulConfig.Value.Tags
            };

            if (checkConfigs != null && checkConfigs.Any())
            {
                registration.Checks = checkConfigs.Select(p => p.GetRegistration()).ToArray();
            }

            RegisterService(registration, consulClient, logger);

            lifetime.ApplicationStopping.Register(() =>
            {
                logger.LogInformation("Deregistering from Consul");
                consulClient.Agent.ServiceDeregister(registration.ID).Wait();
            });

            return app;
        }

        public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration config)
        {
            services.Configure<ConsulConfig>(config);

            services.AddSingleton<IConsulClient, ConsulClient>(p =>
            {
                var c = p.GetService<IOptions<ConsulConfig>>();
                return new ConsulClient(option =>
                {
                    option.Address = new Uri(c.Value.Address);
                });
            });

            return services;
        }

        private static async void RegisterService(AgentServiceRegistration registration, IConsulClient consulClient, ILogger logger)
        {
            var policy = Policy.Handle<Exception>().WaitAndRetryForeverAsync(retry => TimeSpan.FromSeconds(10), (exception, timespan) =>
               {
                   var message = exception.Message;
                   if (exception.InnerException != null)
                   {
                       message = exception.InnerException.Message;
                   }
                   logger.LogCritical($"consul service error: {message}, retry after {timespan.Seconds} seconds");
               });

            await policy.ExecuteAsync(async () =>
            {
                logger.LogInformation("Registering with Consul");

                await consulClient.Agent.ServiceDeregister(registration.ID);
                await consulClient.Agent.ServiceRegister(registration);
            });
        }
    }
}
